<?php
/**
 * @Author: gan
 * @Description:
 * @File:  CoreModel
 * @Version: 1.0.0
 * @Date: 2022/4/18 1:32 下午
 */
declare(strict_types=1);

namespace RX\Core;

use EasySwoole\EasySwoole\Config;
use EasySwoole\Mysqli\QueryBuilder;
use EasySwoole\ORM\AbstractModel;
use EasySwoole\ORM\Collection\Collection;
use EasySwoole\ORM\Db\Cursor;
use EasySwoole\ORM\Db\CursorInterface;
use EasySwoole\ORM\Db\Result;
use EasySwoole\ORM\DbManager;
use EasySwoole\ORM\Exception\Exception;
use EasySwoole\Pool\Exception\PoolEmpty;
use RX\Enums\CommonEnum;
use RX\Exception\BizException;

class CoreModel extends AbstractModel
{
    protected $autoTimeStamp     = "datetime";         // 自动时间戳
    protected $createTime        = 'create_at';        // 创建时间
    protected $updateTime        = 'update_at';        // 更新时间
    protected $deleteTime        = 'delete_at';        // 删除时间
    protected $tableName         = null;               // 表名
    protected $aliasDB           = null;               // 别名
    protected $connectionName    = "";                 // 连接名称
    protected bool $globalDeleteScope = true;               // 全局范围搜索

    public function useGlobalDeleteScope($globalDeleteScope = true)
    {
        $this->globalDeleteScope = $globalDeleteScope;
        return $this;
    }

    /**
     * 条件查询
     * [ field => [val,opt] ] or [ field => "xxxx" ] or [ field => [[1,2],"in"] ] ...
     * @param array $param 参数
     * @param bool $isScript 是否强制
     * @return CoreModel
     * @throws BizException
     */
    public function whereDB(array $param = [], bool $isScript = false)
    {
        foreach ($param as $field => $value) {
            $opt = '=';
            if (is_array($value)) {
                $opt   = $value[1] ?? '=';
                $value = $value[0];
            }

            //如果$value值为null或字符串 跳过
            if (!$isScript && (!isset($value) || $value === '')) {
                continue;
            }

            switch (strtolower($opt)) {
                case 'between':
                    if (isset($value[0])) {
                        $this->where($field, $value[0], '>=');
                    }
                    if (isset($value[1])) {
                        $this->where($field, $value[1], '<=');
                    }
                    break;
                case 'like':
                    $this->where($field, "%{$value}%", "LIKE");
                    break;
                case 'left_like':
                    $this->where($field, "{$value}%", "LIKE");
                    break;
                case 'in':
                    if (!is_array($value)) {
                        $value = explode(',', $value);
                    }
                    if (empty($value)) {
                        throw new BizException(CommonEnum::class, CommonEnum::INVALID_PARAMS);
                    }
                    $this->where($field, $value, $opt);
                    break;
                case 'raw':
                    $this->where($value);
                    break;
                default:
                    $this->where($field, $value, $opt);
                    break;
            }
        }
        return self::whereDeleteDB();
    }


    /**
     * 别名
     * @Author: gan
     * @Description:
     * @Version: 1.0.0
     * @Date: 2022/5/19 5:31 下午
     * @param string $alias 名称
     * @return CoreModel
     */
    public function aliasDB(string $alias)
    {
        $this->aliasDB = $alias;
        return $this->alias($alias);
    }

    /**
     * 多条查询
     * @param string $connection 连接名
     * @return array|bool|Collection|Cursor|CursorInterface
     * @throws BizException
     */
    public function select(string $connection = "")
    {
        $e = null;
        try {
            $this->connectionName = self::getReadConnection($connection);
            $info                 = $this->all();
            foreach ($info as $value) {
                if ($value instanceof $this) {
                    $data[] = $value->toArray(false, false);
                    continue;
                }
                $data[] = $value;
            }
            return $data ?? [];
        } catch (PoolEmpty $e) {
            throw new BizException(CommonEnum::class, CommonEnum::SERVER_BUSY, null, $e);
        } catch (BizException $e) {
            throw $e;
        } catch (\Throwable $e) {
            throw new BizException(CommonEnum::class, CommonEnum::SERVER_ERROR, null, $e);
        }
    }

    /**
     * 单条查询
     * @param string $connection 连接名
     * @return array|bool|AbstractModel|Cursor|CursorInterface|null
     * @throws BizException
     */
    public function find(string $connection = "")
    {
        $e = null;
        try {
            $this->connectionName = self::getReadConnection($connection);
            $result               = $this->get();
            if ($result instanceof $this) {
                return $result->toArray(false, false);
            }
            return $result;
        } catch (PoolEmpty $e) {
            throw new BizException(CommonEnum::class, CommonEnum::SERVER_BUSY, null, $e);
        } catch (BizException $e) {
            throw $e;
        } catch (\Throwable $e) {
            throw new BizException(CommonEnum::class, CommonEnum::SERVER_ERROR, null, $e);
        }
    }

    /**
     * 分页查询
     * @param int $page 页码
     * @param int $size 页大小
     * @param string $connection 连接名
     * @return array
     * @throws BizException
     */
    public function paginate(int $page = 1, int $size = 20, string $connection = "")
    {
        $e = null;
        try {
            $this->connectionName = self::getReadConnection($connection);
            $model                = $this->page($page, $size)->withTotalCount();
            $list                 = $model->select();
            $total                = $model->lastQueryResult()->getTotalCount();
            return [
                'total' => $total,
                'list'  => $list,
            ];
        } catch (PoolEmpty $e) {
            throw new BizException(CommonEnum::class, CommonEnum::SERVER_BUSY, null, $e);
        } catch (BizException $e) {
            throw $e;
        } catch (\Throwable $e) {
            throw new BizException(CommonEnum::class, CommonEnum::SERVER_ERROR, null, $e);
        }
    }

    /**
     * 添加
     * @param array $data 数据
     * @param string $enm enum
     * @param bool $isStrict 是否强制验证
     * @param string $connection 连接名
     * @return bool|int
     * @throws BizException
     */
    public function add(array $data, $enm, $isStrict = true, string $connection = "")
    {
        $e = null;
        try {
            $this->connectionName = self::getWriteConnection($connection);
            $id                   = $this->data($data)->save();
            if (empty($id) && $isStrict && !is_null($enm)) {
                throw new BizException($enm, $enm::INSERT_ERROR);
            }
            return $id;
        } catch (PoolEmpty $e) {
            throw new BizException(CommonEnum::class, CommonEnum::SERVER_BUSY, null, $e);
        } catch (BizException $e) {
            throw $e;
        } catch (\Throwable $e) {
            $msg = $e->getMessage();
            if (false !== strpos($msg, "Duplicate entry")) {
                throw new BizException($enm, $enm::REPEAT_ERROR, null, $e);
            }
            throw new BizException($enm, $enm::INSERT_ERROR, null, $e);
        }
    }

    /**
     * 编辑
     * @param array $data 数据
     * @param string $enm enum
     * @param bool $isStrict 是否强制验证
     * @param string $connection 连接名
     * @return null
     * @throws BizException
     */
    public function edit(array $data, $enm, bool $isStrict = true, string $connection = "")
    {
        $e = null;
        try {
            $this->connectionName = self::getWriteConnection($connection);
            $this->update($data);
            if (empty($this->lastQueryResult())) {
                throw new BizException($enm, $enm::EDIT_ERROR);
            }
            if ($isStrict && !$this->lastQueryResult()->getAffectedRows()) {
                throw new BizException($enm, $enm::EDIT_ERROR);
            }
            return null;
        } catch (PoolEmpty $e) {
            throw new BizException(CommonEnum::class, CommonEnum::SERVER_BUSY, null, $e);
        } catch (BizException $e) {
            throw $e;
        } catch (\Throwable $e) {
            $msg = $e->getMessage();
            if (false !== strpos($msg, "Duplicate entry")) {
                throw new BizException($enm, $enm::REPEAT_ERROR, null, $e);
            }
            throw new BizException($enm, $enm::EDIT_ERROR, null, $e);
        }
    }


    /**
     * 通用 启用-禁用-删除
     *
     * @param int $id ID
     * @param int $active 状态 0,1,2
     * @param $enm
     * @param bool $isStrict 是否强制
     * @param string $field 变更字段
     * @param string $connection 连接名
     * @return null
     * @throws BizException
     */
    public function setStatus(
        int $id,
        int $active,
        $enm,
        bool $isStrict = true,
        string $field = "is_active",
        string $connection = ""
    ) {
        if (!$id) {
            throw new BizException(CommonEnum::class, CommonEnum::INVALID_PARAMS);
        }
        switch ($active) {
            case '0':
            case '1':
                $editData = [$field => $active];
                break;
            case '2':
                $editData = ['delete_at' => date("Y-m-d H:i:s")];
                break;
            default:
                throw new BizException(CommonEnum::class, CommonEnum::INVALID_PARAMS, "状态值不合法");
        }

        return $this->whereDB(['id' => $id])->edit($editData, $enm, $isStrict, $connection);
    }


    /**
     * 执行sql
     * @param string $sql
     * @param string $connection
     * @return bool|Result
     * @throws BizException
     */
    public static function execute(string $sql, string $connection)
    {
        $e = null;
        try {
            $queryBuild = new QueryBuilder();
            $queryBuild->raw($sql);
            return DbManager::getInstance()->query($queryBuild, true, $connection);
        } catch (PoolEmpty $e) {
            throw new BizException(CommonEnum::class, CommonEnum::SERVER_BUSY, null, $e);
        } catch (BizException $e) {
            throw $e;
        } catch (\Throwable $e) {
            throw new BizException(CommonEnum::class, CommonEnum::SERVER_ERROR, null, $e);
        }
    }

    /**
     * 开启事务
     * @param string $connection
     * @param float|null $timeout 超时
     * @throws \Throwable
     * @throws Exception
     * @throws PoolEmpty
     */
    public static function transStart(string $connection, float $timeout = 5.0)
    {
        DbManager::getInstance()->startTransaction($connection, $timeout);
    }

    /**
     * 提交事务
     * @param string $connection
     * @throws \Throwable
     */
    public static function transCommit(string $connection)
    {
        DbManager::getInstance()->commit($connection);
    }

    /**
     * 回滚事务
     * @param string $connection
     * @throws \Throwable
     */
    public static function transRollback(string $connection)
    {
        DbManager::getInstance()->rollback($connection);
    }


    /**
     * @param callable $actions 回调函数
     * @param string $connection 链接
     * @param float|null $timeout 超时
     * @return false|mixed
     * @throws \Throwable
     */
    public static function action(callable $actions, string $connection, float $timeout = 5.0)
    {
        if (!is_callable($actions)) {
            return false;
        }
        $e = null;
        try {
            self::transStart($connection, $timeout);
            $result = call_user_func($actions);
            if ($result === false) {
                self::transRollback($connection);
                return false;
            } else {
                self::transCommit($connection);
                return $result;
            }
        } catch (PoolEmpty $e) {
            self::transRollback($connection);
            throw new BizException(CommonEnum::class, CommonEnum::SERVER_BUSY, null, $e);
        } catch (BizException $e) {
            self::transRollback($connection);
            throw $e;
        } catch (\Throwable $e) {
            self::transRollback($connection);
            throw new BizException(CommonEnum::class, CommonEnum::SERVER_ERROR, null, $e);
        }
    }


    /**
     * 删除where条件
     * @Author: gan
     * @Description:
     * @Version: 1.0.0
     * @Date: 2022/5/19 5:34 下午
     */
    private function whereDeleteDB()
    {
        if (!$this->globalDeleteScope) {
            return $this;
        }
        $deleteField = $this->deleteTime;
        if (isset($this->aliasDB)) {
            $deleteField = $this->aliasDB . '.' . $deleteField;
        }
        $this->where($deleteField, null, 'IS');
        return $this;
    }


    /**
     * 获取读连接
     * @Author: gan
     * @Description:
     * @Version: 1.0.0
     * @Date: 2022/6/3 2:02 下午
     * @param string $connection 连接名
     * @return mixed|string
     * @throws BizException
     */
    public static function getReadConnection(string $connection = "")
    {
        if (!empty($connection)) {
            return $connection;
        }
        $databases = Config::getInstance()->getConf("DATABASE");
        $keys      = array_keys($databases);
        $len       = count($keys);
        if ($len < 1) {
            throw new BizException(CommonEnum::class, CommonEnum::INVALID_PARAMS, "请检查数据库配置是否有效");
        }
        if ($len == 1) {
            return $keys[0];
        }
        // 随机读是否包含写库
        $isContainMaster = $databases[$keys[0]]["isContainMaster"] ?? false;
        if (!$isContainMaster) {
            unset($keys[0]);
        }
        return $keys[array_rand($keys, 1)];
    }


    /**
     * 获取写连接
     * @Author: gan
     * @Description:
     * @Version: 1.0.0
     * @Date: 2022/6/3 2:03 下午
     * @param string $connection 连接名
     * @return mixed|string
     * @throws BizException
     */
    public static function getWriteConnection(string $connection = "")
    {
        if (!empty($connection)) {
            return $connection;
        }
        $databases = Config::getInstance()->getConf("DATABASE");
        $keys      = array_keys($databases);
        $len       = count($keys);
        if ($len < 1) {
            throw new BizException(CommonEnum::class, CommonEnum::INVALID_PARAMS, "请检查数据库配置是否有效");
        }
        return $keys[0];
    }


    /**
     * 设置写连接
     * @Author: gan
     * @Description:
     * @Version: 1.0.0
     * @Date: 2022/7/29 2:17 下午
     * @param string $connection 连接名
     * @param bool $isTemp 是否临时
     * @return CoreModel
     * @throws BizException
     */
    public function setWriteConnection(string $connection = "", bool $isTemp = true)
    {
        $this->connection(self::getWriteConnection($connection), $isTemp);
        return $this;
    }


    /**
     * 设置读连接
     * @Author: gan
     * @Description:
     * @Version: 1.0.0
     * @Date: 2022/7/29 2:17 下午
     * @param string $connection 连接名
     * @param bool $isTemp 是否临时
     * @return CoreModel
     * @throws BizException
     */
    public function setReadConnection(string $connection = "", bool $isTemp = true)
    {
        $this->connection(self::getReadConnection($connection), $isTemp);
        return $this;
    }
}
